package com.sychip.fhc.app.data.repo.impl


import com.fhc.base.YMDHMS
import com.fhc.base.ymdHMS
import com.sychip.fhc.app.data.repo.BleDeviceRepo
import com.sychip.fhc.app.data.source.convert.toDto
import com.sychip.fhc.app.data.source.convert.toEntity
import com.sychip.fhc.app.data.source.dao.BleDeviceDao
import com.sychip.fhc.app.data.source.dto.BleDeviceDto
import com.sychip.fhc.di.ApplicationScope
import com.sychip.fhc.di.DefaultDispatcher
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.util.UUID
import javax.inject.Inject
import javax.inject.Singleton

/**
 * Default implementation of [BleDeviceRepo]. Single entry point for managing tasks' data.
 *
 * @param bleDeviceDao - The local data source
 * @param dispatcher - The dispatcher to be used for long running or complex operations, such as ID
 * generation or mapping many models.
 * @param scope - The coroutine scope used for deferred jobs where the result isn't important, such
 * as sending data to the network.
 */
@Singleton
class BleDeviceRepoImpl @Inject constructor(
    private val bleDeviceDao: BleDeviceDao,
    @DefaultDispatcher private val dispatcher: CoroutineDispatcher,
    @ApplicationScope private val scope: CoroutineScope,
) : BleDeviceRepo {
    override fun getAllBleDevices(): Flow<List<BleDeviceDto>> = flow {

        delay(1500)
        var devList =bleDeviceDao.getAll().toDto()
        Timber.i("   getAllBleDevices %s", devList.size)
        emit(devList)

//
//        var devList = listOf(
//            BleDeviceDto(
//                name = "Device 11",
//                mac = "11-22-33-44",
//                devId = "11",
//            ),
//            BleDeviceDto(
//                name = "Device 22",
//                mac = "22-33-44-55",
//                devId = "22",
//            ),
//        )
//
//        emit(devList.shuffled())
//        devList = listOf(
//            BleDeviceDto(
//                name = "Device 33",
//                mac = "33-44-55-66",
//                devId = "33",
//            ),
//            BleDeviceDto(
//                name = "Device 44",
//                mac = "44-55-66-77",
//                devId = "44",
//            ),
//            BleDeviceDto(
//                name = "Device 55",
//                mac = "55-66-77-88",
//                devId = "55",
//            )
//        )
//        delay(1000)
//        emit(devList.shuffled())

//        delay(5000)
//        throw Exception("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
    }
    
    override fun createBleDevice(name: String, mac: String): Flow<BleDeviceDto> = flow {
        // ID creation might be a complex operation so it's executed using the supplied
        // coroutine dispatcher
        val devId = withContext(dispatcher) {
            UUID.randomUUID().toString()
        }
        val task = BleDeviceDto(
            name = name,
            mac = mac,
            devId = devId,
            lastConnected = System.currentTimeMillis().ymdHMS(),
        )
        bleDeviceDao.upsert(task.toEntity())
        emit(task)
    }

    override suspend fun updateBleDevice(devId: String, name: String, mac: String, lastConnected: String) {
        val task = getBleDevice(devId)?.copy(
            name = name,
            mac = mac,
            lastConnected = lastConnected
        ) ?: throw Exception("BleDevice (id $devId) not found")

        bleDeviceDao.upsert(task.toEntity())
        
    }

    override suspend fun getBleDevices(forceUpdate: Boolean): List<BleDeviceDto> {
        if (forceUpdate) {
            refresh()
        }
        return withContext(dispatcher) {
            bleDeviceDao.getAll().toDto()
        }
    }

    override fun getBleDevicesStream(): Flow<List<BleDeviceDto>> {
        return bleDeviceDao.observeAll().map { tasks ->
            withContext(dispatcher) {
                tasks.toDto()
            }
        }
    }

    override suspend fun refreshBleDevice(devId: String) {
        refresh()
    }

    override fun getBleDeviceStream(devId: String): Flow<BleDeviceDto?> {
        return bleDeviceDao.observeById(devId).map { it.toDto() }
    }

    /**
     * Get a BleDevice with the given ID. Will return null if the task cannot be found.
     *
     * @param devId - The ID of the task
     * @param forceUpdate - true if the task should be updated from the network data source first.
     */
    override suspend fun getBleDevice(devId: String, forceUpdate: Boolean): BleDeviceDto? {
        if (forceUpdate) {
            refresh()
        }
        return bleDeviceDao.getById(devId)?.toDto()
    }

    override suspend fun updateLastConnected(devId: String) {
        bleDeviceDao.updateLastConnected(devId = devId, lastConnected =  System.currentTimeMillis().ymdHMS(),)
        
    }



    override suspend fun clearCompletedBleDevices() {
        bleDeviceDao.deleteCompleted()
        
    }

    override suspend fun deleteAllBleDevices() {
        bleDeviceDao.deleteAll()
        
    }

    override suspend fun deleteBleDevice(devId: String):Int {
        return bleDeviceDao.deleteById(devId)
    }

    /**
     * The following methods load tasks from (refresh), and save tasks to, the network.
     *
     * Real apps may want to do a proper sync, rather than the "one-way sync everything" approach
     * below. See https://developer.android.com/topic/architecture/data-layer/offline-first
     * for more efficient and robust synchronisation strategies.
     *
     * Note that the refresh operation is a suspend function (forces callers to wait) and the save
     * operation is not. It returns immediately so callers don't have to wait.
     */

    /**
     * Delete everything in the local data source and replace it with everything from the network
     * data source.
     *
     * `withContext` is used here in case the bulk `toLocal` mapping operation is complex.
     */
    override suspend fun refresh() {
        withContext(dispatcher) {
//            val remoteBleDevices = networkDataSource.loadBleDevices()
//            bleDeviceDao.deleteAll()
//            bleDeviceDao.upsertAll(remoteBleDevices.toEntity())
        }
    }


}
